package com.delos.rbac.security;

import com.delos.rbac.context.RequestHolder;
import com.delos.rbac.error.ExceptionEnum;
import com.delos.rbac.error.ServiceException;
import com.delos.rbac.service.IResourceService;
import com.delos.rbac.util.JwtUtils;
import com.delos.rbac.vo.UserRoleVO;
import com.delos.rbac.vo.UserVO;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Set;

/**
 * Token处理工具类
 *
 * @author zouyaowen
 * @date 2020-06-27
 */
@Component
@Slf4j
public class TokenManager {

    private static final String USER_ID = "userId";
    private static final String USER_NAME = "userName";
    private static final String USER_ROLE_LIST = "userRoleList";

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private IResourceService resourceService;

    private static final String TOKEN = "token";

    private String resolveToken(HttpServletRequest request) {
        String token = request.getHeader("Authentication");
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(TOKEN);
            if (StringUtils.isEmpty(token)) {
                throw new ServiceException(ExceptionEnum.AUTHENTICATION_NOT_EXIST);
            }
        }
        return token;
    }

    /**
     * 认证过程
     *
     * @param tokenAuth 请求对象信息
     */
    public void parseToken(TokenAuthentication tokenAuth) {
        TokenAuthentication.Credentials credentials = (TokenAuthentication.Credentials) tokenAuth.getCredentials();
        HttpServletRequest request = credentials.getRequest();
        String token = this.resolveToken(request);
        log.info("用户认证信息token=[{}]", token);

        //验证token
        Boolean expired = jwtUtils.isExpired(token);
        if (!expired) {
            throw new ServiceException(ExceptionEnum.AUTHENTICATION_EXPIRED);
        }
        //1、获取Token角色列表
        Set<String> roleSet = getRoleSet(token);
        //2、获取url可以访问的角色列表
        String requestUri = request.getRequestURI();
        Set<String> roleSetByUri = resourceService.getRoleSetByUri(requestUri);
        //3、验证角色之间是否有交集,没有交集即没有权限
        if (CollectionUtils.isEmpty(roleSet) && CollectionUtils.isEmpty(roleSetByUri)) {
            tokenAuth.setAuthenticated(true);
        } else if (!CollectionUtils.isEmpty(roleSet) && !CollectionUtils.isEmpty(roleSetByUri)) {
            roleSet.retainAll(roleSetByUri);
            if (CollectionUtils.isEmpty(roleSet)) {
                throw new ServiceException(ExceptionEnum.UN_AUTHORIZATION);
            } else {
                tokenAuth.setAuthenticated(true);
            }
        } else {
            throw new ServiceException(ExceptionEnum.UN_AUTHORIZATION);
        }
        //4、将认证信息中的用户信息存入上下文
        Long userId = getUserId(token);
        tokenAuth.setUserId(userId);
        String userName = getUserName(token);
        tokenAuth.setUserName(userName);

        //设置用户信息进入当前线程
        UserVO userVO = new UserVO();
        userVO.setId(userId);
        userVO.setUserName(userName);
        userVO.setRoleList(roleSet);
        RequestHolder.setUserInfo(userVO);

        SecurityContextHolder.getContext().setAuthentication(tokenAuth);
    }

    /**
     * 获取用户名称
     *
     * @param token 用户认证信息
     * @return 用户名称
     */
    private String getUserName(String token) {

        Claims claims = jwtUtils.getClaims(token);
        String userName = claims.get(USER_NAME, String.class);
        if (userName == null) {
            throw new ServiceException(ExceptionEnum.AUTHENTICATION_ERROR);
        }
        return userName;
    }

    /**
     * 获取用户ID
     *
     * @param token 认证信息
     * @return 用户系统ID
     */
    private Long getUserId(String token) {
        if (StringUtils.isEmpty(token)) {
            throw new ServiceException(ExceptionEnum.AUTHENTICATION_NOT_EXIST);
        }
        //JWT存储Long数据类型取出来的是Integer，所以系统ID存储为String类型再转
        Claims claims = jwtUtils.getClaims(token);
        String userIdStr = claims.get(USER_ID, String.class);
        if (StringUtils.isEmpty(userIdStr)) {
            throw new ServiceException(ExceptionEnum.AUTHENTICATION_ERROR);
        }
        return Long.valueOf(userIdStr);
    }

    /**
     * 获取token对应的token集合
     *
     * @param token 用户认证信息
     * @return 角色集合
     */
    private Set<String> getRoleSet(String token) {
        if (StringUtils.isEmpty(token)) {
            return Collections.emptySet();
        }
        return jwtUtils.getRoleList(token);
    }

    /**
     * 刷新token
     *
     * @param request  请求对象
     * @param response 相应对象
     */
    public void tryRefreshToken(HttpServletRequest request, HttpServletResponse response) {
    }
}
